package com.gomoku;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// 服务器
class Server extends ServerSocket implements Runnable {
    private final Map<String, Client> mOnlineUsers; // 在线用户列表

    /**
     * 构造方法。
     */
    public Server() throws IOException {
        super();
        mOnlineUsers = new HashMap<>();
    }

    /**
     * 析构方法。
     */
    protected void finalize() {
        // 关闭套接字
        try {
            super.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 清空在线用户列表
        mOnlineUsers.clear();
    }

    /**
     * 处理寻找其它用户请求。
     * @param request   请求
     * @param client    客户端
     */
    private void handleFindUsers(FindUsersRequest request, Client client) {
        // 创建结果
        final List<String> rivalList = new ArrayList<>(mOnlineUsers.size());
        for (HashMap.Entry<String, Client> entry: mOnlineUsers.entrySet()) {
            // 跳过自己的名字
            if (request.mUsername.equals(entry.getKey())) {
                continue;
            }
            rivalList.add(entry.getKey());
        }
        final FindUsersResult result = new FindUsersResult(rivalList);

        // 发送结果
        client.sendData(result);
    }

    /**
     * 处理登录请求。
     * @param request   请求
     * @param client    客户端
     */
    private void handleLogon(LogonRequest request, Client client) {
        LogonResult result;

        // 如果已登录，则直接返回
        if (mOnlineUsers.get(request.mUsername) != null) {
            result = new LogonResult("");
            client.sendData(result);
            return;
        }

        // 保存用户
        mOnlineUsers.put(request.mUsername, client);

        // 创建结果
        result = new LogonResult(request.mUsername);

        // 发送结果
        client.sendData(result);
    }

    /**
     * 处理协议。
     * @param pro   协议
     */
    private void handleProtocol(GomokuProtocol pro) {
        // 如果目标用户不在线则返回
        final Client rivalUser = mOnlineUsers.get(pro.mUsername);
        if (rivalUser == null) {
            return;
        }

        // 发送给目标用户
        rivalUser.sendData(pro);
    }

    /**
     * 处理退出游戏请求。
     * @param request   请求
     * @param client    客户端
     */
    private void handleQuitGame(QuitGame request, Client client) {
        // 广播该用户的请求
        for (HashMap.Entry<String, Client> i: mOnlineUsers.entrySet()) {
            i.getValue().sendData(request);
        }

        // 将该用户从在线列表移除
        mOnlineUsers.remove(request.mUsername);

        // 关闭客户端
        try {
            client.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理客户端发送来的数据。
     * @param pro       协议
     * @param client    客户端
     */
    public synchronized void onGetMessageFromClient(GomokuProtocol pro, Client client) {
        switch (pro.mType) {
            case FIND_USERS:
                this.handleFindUsers((FindUsersRequest)pro, client);
                break;
            case LOGON:
                this.handleLogon((LogonRequest)pro, client);
                break;
            case QUIT_GAME:
                this.handleQuitGame((QuitGame)pro, client);
                break;
            default:
                this.handleProtocol(pro);
                break;
        }
    }

    /**
     * 接受客户端连接的线程。
     */
    @Override
    public void run() {
        Socket clientSocket;
        while (!super.isClosed()) {
            try {
                clientSocket = super.accept();
                new Client(this, clientSocket).start();
            } catch (Exception e) {
                break;
            }
        }
    }

    /**
     * 开启服务。
     * @return 成功则返回 true，否则返回 false
     */
    public boolean start() {
        // 绑定 IP 地址和端口号
        try {
            final InetAddress inetAddress = InetAddress.getByName(GomokuProtocol.SERVER_IP);
            final SocketAddress serverAddr = new InetSocketAddress(inetAddress, GomokuProtocol.SERVER_PORT);
            super.bind(serverAddr);
        } catch (Exception e) {
            return false;
        }

        // 开启接收数据的线程
        new Thread(this).start();

        return true;
    }
}
